#include "main.h"
#include "co_tests.h"
#include "evq_tests.h"

//**************************************
// test data
//**************************************
/*
 *  Event queues cannot be cleaned up; they are permanent structures.
 *  As a result we will instantiate some in the first test and use
 *  them all in later tests.
 */
evq_queue_t queue1;
static evq_queue_t queue2, queue3;

static void *default_event_data1 = (void *) 0xDEADBABE;
static void *default_event_data2 = (void *) 0xCAFEBEEF;

SUITE(evq);

//**************************************
// TEST 1
//**************************************

TEST evq_test_1(void)
{
    queue1 = evq_create_queue();
    ASSERT_NEQm("queue is not NULL", queue1, NULL);

    queue2 = evq_create_queue();
    ASSERT_NEQm("queue is not NULL", queue2, NULL);

    queue3 = evq_create_queue();
    ASSERT_NEQm("queue is not NULL", queue3, NULL);

    ASSERT_NEQm("queue1 is not queue2", queue1, queue2);
    ASSERT_NEQm("queue2 is not queue3", queue2, queue3);
    ASSERT_NEQm("queue1 is not queue3", queue1, queue3);

    PASSm("queue creation");
}

//**************************************
// TEST 2
//**************************************

static void test_2_handler1(void *data)
{
}

TEST evq_test_2(void)
{
    evq_event_t e = evq_create_fn_event(0, 0, test_2_handler1, default_event_data1);
    ASSERT_NEQm("created event is not null", e, NULL);

    evq_destroy_event(e);

    PASSm("callback event creation");
}

//**************************************
// TEST 3
//**************************************

static void test_3_handler(void *data)
{
}

TEST evq_test_3(void)
{
    co_t co = co_create(test_3_handler, STACK_SIZE, NULL,NULL);
    evq_event_t e = evq_create_co_event(0, 0, co, default_event_data2);
    ASSERT_NEQm("created event is not NULL", e, NULL);

    evq_destroy_event(e);
    co_destroy(&co);

    PASSm("coroutine event creation");
}

//**************************************
// TEST 4
//**************************************
static volatile bool test_4_flag;

static void *test_4_handler1_data;
static void test_4_handler1(void *data)
{
    test_4_handler1_data = data;
    test_4_flag = true;
}

static void *test_4_handler2_data;
static void test_4_handler2(void *data)
{
    test_4_handler2_data = data;
    test_4_flag = true;
}

TEST evq_test_4(void)
{
    evq_event_t e = evq_create_fn_event(0, 0, test_4_handler1, default_event_data1);

    test_4_flag = false;
    evq_post_event(queue1, e);
    evq_process_one_tick(queue1, NULL);
    ASSERTm("test flag was set", test_4_flag);
    ASSERT_EQm("test data was correct", test_4_handler1_data, default_event_data1);

    test_4_flag = false;
    evq_fill_fn_event(e, 0, 0, test_4_handler2, default_event_data2);
    evq_post_event(queue1, e);
    evq_process_one_tick(queue1, NULL);
    ASSERTm("test flag was set", test_4_flag);
    ASSERT_EQm("test data was correct", test_4_handler2_data, default_event_data2);

    evq_destroy_event(e);

    PASSm("static callback event on single queue");
}

//**************************************
// TEST 5
//**************************************

static volatile bool test_5_flag;

static void *test_5_handler1_data;
static void test_5_handler1(void *data)
{
    // this test only fires once so we can safely return and end the coroutine
    test_5_handler1_data = data;
    test_5_flag = true;
}

static void *test_5_handler2_data;
static void test_5_handler2(void *data)
{
    // this test only fires once so we can safely return and end the coroutine
    test_5_handler2_data = data;
    test_5_flag = true;
}

TEST evq_test_5(void)
{
    co_t co1 = co_create(test_5_handler1, STACK_SIZE, NULL,NULL);
    evq_event_t e = evq_create_co_event(0, 0, co1, default_event_data2);
    test_5_flag = false;
    evq_post_event(queue1, e);
    evq_process_one_tick(queue1, NULL);
    ASSERTm("test flag was set", test_5_flag);
    ASSERT_EQm("test data was correct", test_5_handler1_data, default_event_data2);

    static uintptr_t stack_store[STACK_SIZE];
    co_t co2 = co_create(test_5_handler2, STACK_SIZE, stack_store, NULL);
    ASSERT_NEQm("external stack store still creates coroutine", co2, NULL);

    evq_fill_co_event(e, 0, 0, co2, default_event_data1);
    test_5_flag = false;
    evq_post_event(queue1, e);
    evq_process_one_tick(queue1, NULL);
    ASSERTm("test flag was set", test_5_flag);
    ASSERT_EQm("test data was correct", test_5_handler2_data, default_event_data1);

    evq_destroy_event(e);
    co_destroy(&co2);
    co_destroy(&co1);

    PASSm("static coroutine event on single queue");
}

//**************************************
// TEST 6
//**************************************

static volatile bool test_6_flag;

static void *test_6_handler1_data;
static void test_6_handler1(void *data)
{
    test_6_handler1_data = data;
    test_6_flag = true;
}

TEST evq_test_6(void)
{
    test_6_flag = false;
    evq_event_t e = evq_queue_now(queue1, test_6_handler1, default_event_data1);
    ASSERT_EQm("time remaining should be zero", evq_query_time_remaining(e), 0);
    evq_process_one_tick(queue1, NULL);
    ASSERTm("test flag was set", test_6_flag);
    ASSERT_EQm("test data was correct", test_6_handler1_data, default_event_data1);

    evq_destroy_event(e);

    PASSm("dynamic callback event on single queue");
}

//**************************************
// TEST 7
//**************************************
static volatile bool test_7_flag;

static void *test_7_handler1_data;
static void test_7_handler1(void *data)
{
    // this test only fires once so we can safely return and end the coroutine
    test_7_handler1_data = data;
    test_7_flag = true;
}

TEST evq_test_7(void)
{
    co_t co = co_create(test_7_handler1, STACK_SIZE, NULL,NULL);
    test_7_flag = false;
    evq_event_t e = evq_resume_now(queue1, co, default_event_data2);
    ASSERT_EQm("time remaining should be zero", evq_query_time_remaining(e), 0);
    evq_process_one_tick(queue1, NULL);
    ASSERTm("test flag was set", test_7_flag);
    ASSERT_EQm("test data was correct", test_7_handler1_data, default_event_data2);

    evq_destroy_event(e);
    co_destroy(&co);

    PASSm("dynamic coroutine event on single queue");
}

//**************************************
// TEST 8
//**************************************

static volatile bool test_8_flag;

static void *test_8_handler_1_data;
static void test_8_handler1(void *data)
{
    test_8_handler_1_data = data;
    test_8_flag = true;
}

TEST evq_test_8(void)
{
    evq_attach_queue(queue1, queue2);
    evq_attach_queue(queue2, queue3);

    test_8_flag = false;
    evq_event_t e = evq_queue_now(queue1, test_8_handler1, default_event_data1);
    evq_process_one_tick(queue1, NULL);
    ASSERTm("test flag is set", test_8_flag);
    ASSERT_EQm("test data is correct", test_8_handler_1_data, default_event_data1);

    if (!test_8_flag) { goto cleanup; }

    test_8_flag = false;
    e = evq_queue_now(queue2, test_8_handler1, default_event_data2);
    evq_process_one_tick(queue1, NULL);
    ASSERTm("test flag is set", test_8_flag);
    ASSERT_EQm("test data is correct", test_8_handler_1_data, default_event_data2);

    if (!test_8_flag) { goto cleanup; }

    test_8_flag = false;
    e = evq_queue_now(queue3, test_8_handler1, default_event_data1);
    evq_process_one_tick(queue1, NULL);
    ASSERTm("test flag is set", test_8_flag);
    ASSERT_EQm("test data is correct", test_8_handler_1_data, default_event_data1);

    if (!test_8_flag) { goto cleanup; }

    test_8_flag = false;
    e = evq_queue_now(queue1, test_8_handler1, default_event_data2);
    evq_process_one_tick(queue2, NULL);
    ASSERT_FALSEm("test flag is clear", test_8_flag);
    ASSERT_EQm("test data is unchanged", test_8_handler_1_data, default_event_data1);

    if (test_8_flag) { goto cleanup; }

    // clear our event from the right queue
    evq_process_one_tick(queue1, NULL);
    ASSERTm("test flag is set", test_8_flag);
    ASSERT_EQm("test data is correct", test_8_handler_1_data, default_event_data2);

cleanup:
    evq_destroy_event(e);
    evq_detach_queue(queue3);
    evq_detach_queue(queue2);

    PASSm("callback events through chained queues");
}

//**************************************
// TEST 9
//**************************************

static volatile bool test_9_flag;

static void *test_9_handler1_data;
static void test_9_handler1(void *data)
{
    // this test fires multiple times so we need to yield and loop
    while (true)
    {
        test_9_handler1_data = data;
        test_9_flag = true;
        data = co_yield(data);
    }
}

TEST evq_test_9(void)
{
    co_t co = co_create(test_9_handler1, STACK_SIZE, NULL, NULL);
    evq_attach_queue(queue1, queue2);
    evq_attach_queue(queue2, queue3);

    test_9_flag = false;
    evq_event_t e = evq_resume_now(queue1, co, default_event_data2);
    evq_process_one_tick(queue1, NULL);
    ASSERTm("test flag is set", test_9_flag);
    ASSERT_EQm("test data is correct", test_9_handler1_data, default_event_data2);

    if (!test_9_flag) { goto cleanup; }

    test_9_flag = false;
    e = evq_resume_now(queue2, co, default_event_data1);
    evq_process_one_tick(queue1, NULL);
    ASSERTm("test flag is set", test_9_flag);
    ASSERT_EQm("test data is correct", test_9_handler1_data, default_event_data1);

    if (!test_9_flag) { goto cleanup; }

    test_9_flag = false;
    e = evq_resume_now(queue3, co, default_event_data2);
    evq_process_one_tick(queue1, NULL);
    ASSERTm("test flag is set", test_9_flag);
    ASSERT_EQm("test data is correct", test_9_handler1_data, default_event_data2);

    if (!test_9_flag) { goto cleanup; }

    test_9_flag = false;
    e = evq_resume_now(queue1, co, default_event_data1);
    evq_process_one_tick(queue2, NULL);
    ASSERT_FALSEm("test flag is clear", test_9_flag);
    ASSERT_NEQm("test data is unchanged", test_9_handler1_data, default_event_data1);

    if (test_9_flag) { goto cleanup; }

    // we need to clear our event from the right queue
    evq_process_one_tick(queue1, NULL);
    ASSERTm("test flag is set", test_9_flag);
    ASSERT_EQm("test data is correct", test_9_handler1_data, default_event_data1);

    if (!test_9_flag) { goto cleanup; }

cleanup:
    evq_destroy_event(e);
    evq_detach_queue(queue3);
    evq_detach_queue(queue2);
    co_destroy(&co);

    PASSm("coroutine events through chained queues");
}

//**************************************
// TEST 10
//**************************************

#define TEST_10_DELAY 20

static volatile bool test_10_flag;

static void *test_10_handler1_data;
static void test_10_handler1(void *data)
{
    test_10_handler1_data = data;
    test_10_flag = true;
}


TEST evq_test_10(void)
{
    test_10_flag = false;
    evq_event_t e = evq_queue_in(queue1, test_10_handler1, default_event_data1, TEST_10_DELAY);
    ASSERT_EQm("query time is correct", evq_query_time_remaining(e), TEST_10_DELAY);

    tick_ms before = evq_get_tick();

    do {evq_process_one_tick(queue1, NULL); }
    while (!test_10_flag);

    tick_ms after = evq_get_tick();
    ASSERT_EQm("delay expired properly", (after - before), TEST_10_DELAY);
    ASSERTm("test flag is set", test_10_flag);
    ASSERT_EQm("test data is correct", test_10_handler1_data, default_event_data1);

    evq_destroy_event(e);

    PASSm("delayed callback event");
}

//**************************************
// TEST 11
//**************************************

#define TEST_11_DELAY 5

static volatile bool test_11_flag;

static void *test_11_handler1_data;
static void test_11_handler1(void *data)
{
    test_11_handler1_data = data;
    test_11_flag = true;
}


TEST evq_test_11(void)
{
    test_11_flag = false;
    co_t co = co_create(test_11_handler1, STACK_SIZE, NULL,NULL);
    evq_event_t e = evq_resume_in(queue1, co, default_event_data2, TEST_11_DELAY);
    ASSERT_EQm("query time is correct", evq_query_time_remaining(e), TEST_11_DELAY);

    tick_ms before = evq_get_tick();

    do {evq_process_one_tick(queue1, NULL); }
    while (!test_11_flag);

    tick_ms after = evq_get_tick();
    ASSERT_EQm("delay expired properly", (after - before), TEST_11_DELAY);
    ASSERTm("test flag is set", test_11_flag);
    ASSERT_EQm("test data is correct", test_11_handler1_data, default_event_data2);

    evq_destroy_event(e);

    PASSm("delayed coroutine event");
}

//**************************************
// TEST 12
//**************************************

static volatile bool test_12_flag;

#define TEST_12_DELAY 50

void *test_12_handler1_data;
static void test_12_handler1(void *data)
{
    test_12_handler1_data = data;
    test_12_flag = true;
}

TEST evq_test_12(void)
{
    test_12_flag = false;
    evq_event_t e = evq_queue_in(queue1, test_12_handler1, default_event_data1, TEST_12_DELAY);
    ASSERT_EQm("query time is correct", evq_query_time_remaining(e), TEST_12_DELAY);
    tick_ms before = evq_get_tick();

    while ((evq_get_tick() - before) < (TEST_12_DELAY >> 1))
    {
        evq_process_one_tick(queue1, NULL);
        ASSERT_FALSEm("test flag is clear", test_12_flag);
        ASSERT_NEQm("test data is not set", test_12_handler1_data, default_event_data1);

        if (test_12_flag) { goto cleanup; }
    }

    ASSERT_NEQm("query time is correct", evq_query_time_remaining(e), TEST_12_DELAY);
    evq_cancel_event(e);

    while ((evq_get_tick() - before) < (TEST_12_DELAY))
    {
        evq_process_one_tick(queue1, NULL);
        ASSERT_FALSEm("test flag is clear", test_12_flag);

        if (test_12_flag) { goto cleanup; }
    }

cleanup:
    evq_destroy_event(e);

    PASSm("cancelling an event");
}

//**************************************
// TEST 13
//**************************************

static volatile bool test_13_flag;

static void *test_13_handler1_data;
static void test_13_handler1(void *data)
{
    test_13_handler1_data = data;
    test_13_flag = true;
}

TEST evq_test_13(void)
{
    evq_attach_queue(queue1, queue2);
    evq_attach_queue(queue2, queue3);

    // queue3 -> queue1
    test_13_flag = false;
    evq_event_t e = evq_queue_now(queue3, test_13_handler1, default_event_data1);
    evq_process_one_tick(queue1, NULL);
    ASSERTm("test flag is set", test_13_flag);
    ASSERT_EQm("test data is set", test_13_handler1_data, default_event_data1);

    if (!test_13_flag) { goto cleanup; }

    evq_detach_queue(queue3);

    test_13_flag = false;
    e = evq_queue_now(queue3, test_13_handler1, default_event_data2);
    evq_process_one_tick(queue2, NULL);
    ASSERT_FALSEm("test flag is clear", test_13_flag);
    ASSERT_NEQm("test data is not set", test_13_handler1_data, default_event_data2);

    if (test_13_flag) { goto cleanup; }

    evq_process_one_tick(queue3, NULL);
    ASSERTm("test flag is set", test_13_flag);
    ASSERT_EQm("test data is set", test_13_handler1_data, default_event_data2);

    if (!test_13_flag) { goto cleanup; }

cleanup:
    evq_destroy_event(e);
    evq_detach_queue(queue2);

    PASSm("detaching a queue");
}

//**************************************
// TEST 14
//**************************************

#define TEST_14_DELAY 50

static volatile bool test_14_flag;

static void *test_14_handler1_data;
static void test_14_handler1(void *data)
{
    test_14_handler1_data = data;
    test_14_flag = true;
}

TEST evq_test_14(void)
{
    test_14_flag = false;
    evq_event_t e = evq_queue_now(queue1, test_14_handler1, default_event_data1);
    tick_ms before = evq_get_tick();
    evq_process_for(queue1, TEST_14_DELAY, NULL);
    ASSERTm("test flag is set", test_14_flag);
    ASSERT_EQm("test data is set", test_14_handler1_data, default_event_data1);
    ASSERT_EQm("processing time is correct", (evq_get_tick() - before), TEST_14_DELAY);

    evq_destroy_event(e);

    PASSm("time-delimited processing");
}

//**************************************
// TEST 15
//**************************************

#define TEST_15_DELAY 10

static volatile int test_15_counter;

static void *test_15_handler1_data;
static void test_15_handler1(void *data)
{
    test_15_handler1_data = data;
    test_15_counter++;
}

TEST evq_test_15(void)
{
    test_15_counter = 0;
    evq_event_t e = evq_queue_every(queue1, test_15_handler1, default_event_data1, TEST_15_DELAY);
    evq_process_for(queue1, (TEST_15_DELAY * 3) + (TEST_15_DELAY >> 1), NULL);
    ASSERT_EQm("callback was called the right number of times", test_15_counter, 3);
    ASSERT_EQm("test data is set", test_15_handler1_data, default_event_data1);

    evq_destroy_event(e);

    PASSm("repeating callback events");
}

//**************************************
// TEST 16
//**************************************

#define TEST_16_DELAY 50

static volatile int test_16_counter;

static void *test_16_handler1_data;
static void test_16_handler1(void *data)
{
    while (true)
    {
        test_16_handler1_data = data;
        test_16_counter++;
        data = co_yield(data);
    }
}

TEST evq_test_16(void)
{
    test_16_counter = 0;
    co_t co = co_create(test_16_handler1, STACK_SIZE, NULL,NULL);
    evq_event_t e = evq_resume_every(queue1, co, default_event_data2, TEST_16_DELAY);
    evq_process_for(queue1, (TEST_16_DELAY * 3) + (TEST_16_DELAY >> 1), NULL);
    ASSERT_EQm("callback was called the right number of times", test_16_counter, 3);
    ASSERT_EQm("test data is set", test_16_handler1_data, default_event_data2);

    evq_destroy_event(e);
    co_destroy(&co);

    PASSm("repeating coroutine events");
}

//**************************************
// TEST 17
//**************************************

static volatile int test17_idle_counter;

static void test_17_idle_callback(void)
{
    ++test17_idle_counter;
}

TEST evq_test_17(void)
{
    evq_process_one_tick(queue1, test_17_idle_callback);
    ASSERT_EQm("idle routine was called once", test17_idle_counter, 1);

    PASSm("idle callback handling");
}

SUITE(evq)
{
    RUN_TEST(evq_test_1);
    RUN_TEST(evq_test_2);
    RUN_TEST(evq_test_3);
    RUN_TEST(evq_test_4);
    RUN_TEST(evq_test_5);
    RUN_TEST(evq_test_6);
    RUN_TEST(evq_test_7);
    RUN_TEST(evq_test_8);
    RUN_TEST(evq_test_9);
    RUN_TEST(evq_test_10);
    RUN_TEST(evq_test_11);
    RUN_TEST(evq_test_12);
    RUN_TEST(evq_test_13);
    RUN_TEST(evq_test_14);
    RUN_TEST(evq_test_15);
    RUN_TEST(evq_test_16);
    RUN_TEST(evq_test_17);
}

#if 0
//**************************************
// test implementations
//**************************************
#endif
